Repository navigation
Phase 2, theme 1: retention markers + prune subprocess + Tools run record, and run identity (RUN-01..07) - #30
Conversation
…ools helpers, and run identity Hosts never delete under results/ and runs/: the host writes .lc-owner.json when a folder is made and .lc-done.json (atomically) when the run ends, then starts `labconstrictor-tools prune` in a subprocess. The done status and the run record come from labconstrictor_tools.runs (Tools >= 0.3.0a1, checked). A run is frozen when Run is pressed (ImageData objects, selected ROIs, plane, areas) and placed only on that image while it is still shown with its exported shape (target_unavailable / target_changed_size). The script is split into files listed in parts.txt; lint joins them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN
Sabotage checksEach row: production code broken in a copy of the final tree of this PR (a regular expression replaced one place), the PR's own GUI body run on it. A row must show failed checks; every row does. "aborted with an exception" means the test body died on the missing marker or folder (the body's own read of it raised), which also fails the body (exit code 1, no RESULTS line of passes).
(The two Generated by Claude Code |
… reports a missing file as a failed check The header still said the script could be pasted into the Script Editor alone. The retention body read the sentinel and marker files of folders with plain reads, so a deleted folder killed the body with an exception instead of failing the named check (found by sabotage). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN
Theme 1 of the wrong-science wave for QuPath: retention markers + the
prunesubprocess + the run record through the Tools helpers (SPEC_WRONG_SCIENCE section 3) and run identity (section 4). The reference is the Napari work (LabConstrictor-napari #51 and #57). Number limits (theme 2) and async lookups + compatibility + conformance tests (theme 3) follow in two more PRs that are stacked on this one (see the end).Fixes #14. Refs #16 (already closed: this PR adds the end-of-run half of it).
What changes
Retention: the host never deletes, it marks and asks Tools
LcRetention(fileLcRetention.groovy):.lc-owner.jsonis written first when a folder is made (results folder innewJobDir, run-record folder at the start of the run),.lc-done.jsonlast and atomically (temp file, then rename). The formats are those of section 3.2 / 3.8 (and checked by Tools itself in the tests, see below).labconstrictor_tools.runs.done_status(COMPLETE ok, CANCELED cancelled, FAILED failed, CRASHED cancelled after Cancel and failed otherwise). A worker that was killed after Cancel keeps its crash text in the run record (statusCRASHED) and the done statuscancelled; closing the window during a run is a cancellation too (Napari does the same).<app python> -m labconstrictor_tools prunein a daemon thread: it does not wait, ignores the exit code except to log a failure (prune ended with exit code N,prune could not be started) and a failure never fails or delays the run. If the subprocess cannot start the folders stay: there is no code in the script that deletes anything underresults/orruns/(a test scans the sources for it).LcRunsmoved to the new file and no longer writes the record itself: the record is built by the Tools helpersruns.build_record+record_jsonin the app's own Python (LcRunRecord, a smallpython -chelper),host: "qupath"is added, andrun_id/started_utcare passed. If the helper cannot run, the status line says so (the run record could not be written (...); its folders stay as they are and are never removed) and the folders get no done marker (never pruned).LcCompat(a Groovy port ofcompat.check_schema) refuses a run whose app has Tools older than 0.3.0a1 or withoutretention_prune, with the Tools sentence (this app's labconstrictor-tools 0.2.0rc1 is older than the 0.3.0a1 this host needs: update it). PR 3 applies the same check when the registry is read.results/andruns/(no marker, soprunecould never remove them). Rewritten. Folders written by older versions have no marker and are never cleaned automatically (spec 3.3, "legacy folders ... never auto-deleted"; Tools has a follow-up itemclean --legacy).Run identity (RUN-01..07, issue #16)
LcRun(LcRunIdentity.groovy), frozen when Run is pressed on the FX thread, before any worker call:run_id, app, tool, theImageDataobject of every "Current image" input (+ its shape), the ROIs of the selected annotations (immutable objects, so by value), the plane and the Image areas. Export, the RegionOf mask, the request, the run record and the placement of results read it, nothing live. The chooser text is never identity.ImageDataonly while a viewer still shows it with the shape it was exported with; otherwise nothing is drawn, the files stay in the owned results folder, and the status is the conformance sentencetarget_unavailable(gone) ortarget_changed_size(still shown, other shape), never swapped, with therun_idand the path; the code[target_unavailable]is logged. An image that is open in a second viewer gets the results even if the active viewer shows another image.labconstrictor.run_id,.appand.toolof the run (metadata), the results window keeps the run id, the run record hasrun_idandstarted_utc. A tool or app switched while a run goes on never changes what the run records or draws and its end no longer clears the fields of the tool shown now (ClearAfterRun).'blobs.tif' is no longer the image open in QuPath (no image is open): choose the image again); the same refusal protects the case where the chooser and the open image disagree at the click.Structure
parts.txt: the main script first, thenLcCompat,LcRetention,LcRunIdentity), joined by the extension, bytests/run_gui_test.shand bybuild.sh(which also writestarget/LabConstrictorTools-all.groovyfor the Script Editor: the main script alone does not run any more). The main script grows by about 30 lines, all new code is in the new files (each below 600 lines, all new functions typed).lint.ymllints the joined text (CodeNarc reads one file at a time and cannot resolve a class of another file) and fails when a part file reaches 600 lines.Tests
New bodies (CI registers the new
runstest app fromtests/runs_app: delayed tools that wait for a gate file, a tool that ignores Cancel, one that dies):gui_test_retention_body.groovy: HRET-01..06, RET-10, RET-11 read from conformance.json; owner/done markers checked field by field and judged eligible by Tools itself (prune --dry-run --json); the prune replaced by a recorder (saw the done files), by a failing and by a missing command; 60 runs with foreign and 60/120 old folders surviving; the realprune(newest 20 / 50 kept, legacy, foreign, unfinished, renamed-copy and symlinked folders untouched, by file content); a worker killed after Cancel (cancelled, crash text in the record) and one that dies (failed); a failed and a cooperatively cancelled run; the temporary export folder removed; the Tools-too-old refusals; an unbuildable record; a scan that no code deletes under results/runs.gui_test_identity_body.groovy: RUN-01..04, 06, 07 read from conformance.json through the real dialog (a gate file is the barrier): image switched, closed, kept in a second viewer; selection changed during the export (a hook on the run thread) for both the crop and the mask; tool and app switched while running; the shape changed by reflection (nothing else can change anImageData's server); sentences compared with the conformance templates, code logged.Existing tests changed (spec supersedes them)
gui_test_parity_support_body.groovy:LcRuns.record(...)now needs the app's real interpreter (it calls the Tools helper): the three calls pass the registeredinteractionsentry;app_versionis read from the registry by Tools (it used to be the value the test passed).gui_test_parity_host_body.groovy,gui_test_parity_form_body.groovy,gui_test_parity_pixel_body.groovy: the synthetic schemas they register now saytools_versionandfeatures(a run of an app with Tools older than 0.3.0a1 is refused).gui_test_frame_body.groovy,gui_test_area_body.groovy: they count the folders of the results folder, which the realprune(started after every run) now keeps at 20 in the shared registry; both switch the prune to a no-op so the count means what it did (the real prune has its own body).tests/run_gui_ci.sh: two new bodies, and the two duplicated lines of theneedstable removed.Nothing was skipped or loosened.
Sabotage checks
(see the comment on this PR: each break of production code in a copy made the named test fail)
Spec gaps and conservative choices
ImageData's server is fixed); the check is implemented against the sameImageDataand tested by replacing its server with reflection.started_utc/finished_utc:finished_utcis the host's own (ends inZ, as before) rather than the helper's default.no longer the image openrefusal and of the record problem are this host's own.Not verified
hs_err_pid*.logfiles are untouched here (PR Remove two committed JVM crash logs and ignore them #29 removes them).Stack
PR 2 (number limits + exit-code rule) is based on this branch and PR 3 (async + compatibility + conformance) on PR 2. Merge this one as a merge commit and retarget the next.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Y2SN8SuEakTdiWy3F2WUqN
Generated by Claude Code